using System;
using System.Collections.Generic;

namespace Extended.NetRouting
{
    internal static class Tools
    {
        /// <summary>
        /// Selects from the source the item that has the minimum value for the given item property expression
        /// </summary>
        /// <typeparam name="T">The type of items in the source</typeparam>
        /// <typeparam name="K">The type of the item property expression that will be used as criterion for comparison</typeparam>
        /// <param name="source">A list of items</param>
        /// <param name="expression">The item property expression that will be used as criterion for comparison</param>
        /// <returns>The item that has the minimum value for the given property expression. Returns default value of T (null of T is object) if list is empty.</returns>
        /// <exception cref="ArgumentNullException">Source or expression are null</exception>
        public static T SelectByMin<T, K>(this IEnumerable<T> source, Func<T, K> expression)
        {
            return source.SelectByMin(expression, Comparer<K>.Default);
        }

        /// <summary>
        /// Selects from the source the item that has the minimum value for the given item property expression
        /// </summary>
        /// <typeparam name="T">The type of items in the source</typeparam>
        /// <typeparam name="K">The type of the item property expression that will be used as criterion for comparison</typeparam>
        /// <param name="source">A list of items</param>
        /// <param name="expression">The item property expression that will be used as criterion for comparison</param>
        /// <param name="comparer">Instance of a comparer that will do the comparison</param>
        /// <returns>The item that has the minimum value for the given property expression. Returns default value of T (null of T is object) if list is empty.</returns>
        /// <exception cref="ArgumentNullException">Source or expression are null</exception>
        public static T SelectByMin<T, K>(this IEnumerable<T> source, Func<T, K> expression, IComparer<K> comparer)
        {
            return MinMaxBy(source, expression, comparer, false);
        }

        /// <summary>
        /// Selects from the source the item that has the maximum value for the given item property expression
        /// </summary>
        /// <typeparam name="T">The type of items in the source</typeparam>
        /// <typeparam name="K">The type of the item property expression that will be used as criterion for comparison</typeparam>
        /// <param name="source">A list of items</param>
        /// <param name="expression">The item property expression that will be used as criterion for comparison</param>
        /// <returns>The item that has the maximum value for the given property expression. Returns default value of T (null of T is object) if list is empty.</returns>
        /// <exception cref="ArgumentNullException">Source or expression are null</exception>
        public static T SelectByMax<T, K>(this IEnumerable<T> source, Func<T, K> expression)
        {
            return source.SelectByMax(expression, Comparer<K>.Default);
        }

        /// <summary>
        /// Selects from the source the item that has the maximum value for the given item property expression
        /// </summary>
        /// <typeparam name="T">The type of items in the source</typeparam>
        /// <typeparam name="K">The type of the item property expression that will be used as criterion for comparison</typeparam>
        /// <param name="source">A list of items</param>
        /// <param name="expression">The item property expression that will be used as criterion for comparison</param>
        /// <param name="comparer">Instance of a comparer that will do the comparison</param>
        /// <returns>The item that has the maximum value for the given property expression. Returns default value of T (null of T is object) if list is empty.</returns>
        /// <exception cref="ArgumentNullException">Source or expression are null</exception>
        public static T SelectByMax<T, K>(this IEnumerable<T> source, Func<T, K> expression, IComparer<K> comparer)
        {
            return MinMaxBy(source, expression, comparer, true);
        }

        private static T MinMaxBy<T, K>(this IEnumerable<T> source, Func<T, K> expression, IComparer<K> comparer, bool invert)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (expression == null) throw new ArgumentNullException("expression");
            if (comparer == null) throw new ArgumentNullException("comparer");

            // inverter variable will be used to switch between seeking for minimum or maximum value
            var inverter = invert ? -1 : 1;

            using (var iterator = source.GetEnumerator())
            {
                // if iterator is empty then return default value (null if T is object)
                if (!iterator.MoveNext())
                {
                    return default(T);
                }

                // initially consider the first item to be the selection
                var selection = iterator.Current;
                var selectionValue = expression(selection);
                
                // browse the contents of the iterator
                while (iterator.MoveNext())
                {
                    // calculate value for the current item
                    var current = iterator.Current;
                    var currentValue = expression(current);
                    
                    // compare value of current item with the one already selected
                    if (comparer.Compare(currentValue, selectionValue) * inverter < 0)
                    {
                        selection = current;
                        selectionValue = currentValue;
                    }
                }

                return selection;
            }
        }
    }
}